local login = require "loginserver"
local crypt = require "skynet.crypt"
local skynet = require "skynet"
local httpc = require "http.httpc"
local snowflake = require "libsnowflake"

local login_host = skynet.getenv("loginserver_host")
local login_port = skynet.getenv("loginserver_port")
local authenticate_backend = skynet.getenv("loginserver_authenticate_backend")
local authenticate_endpoint= skynet.getenv("loginserver_authenticate_endpoint")
local signup_backend = authenticate_backend
local signup_endpoint = skynet.getenv("loginserver_signup_endpoint")

assert(login_host ~= nil, "loginserver_host must specified in config file")
assert(login_port ~= nil, "loginserver_port must specified in config file")

host = login_host
port = tonumber(login_port)

local login_slave_instance = skynet.getenv("loginserver_slave_instance")
login_slave_instance = login_slave_instance or 8
login_slave_instance = tonumber(login_slave_instance)


local server = {
	host = host,
	port = port, 
	signup_port = tonumber(skynet.getenv("signupserver_port") or 8081),
	multilogin = false,	-- disallow multilogin
	name = "logind",
	instance = login_slave_instance,
}

local server_list = {} -- { gate_servername : gate }
local user_online = {}
local user_login = {}

local online_counts = 0 -- 在线人数

local authenticators = {}

function authenticators.skynetchat(sdk_platform, token) 
	local uid

	local postfields = {
		sdk_platform = sdk_platform,
		token = token,
	}

	local recvheader = {}

	-- httpc.request(method, host, url, recvheader, header, content)
	--[[
		1. method: get or post
		2. host: 目标主机
		3. url: 请求url
		4. recvheader: nil或空表，用于接受回应的http协议头
		5. header: 自定义http请求头，其中没host会用 2.host 参数补
		6. content: 请求内容
	]]
	-- httpc.post(host, url, form, recvheader)
	--[[ source code:
		local header = {
			["content-type"] = "application/x-www-form-urlencoded"
		}
		local body = {}
		for k,v in pairs(form) do
			table.insert(body, string.format("%s=%s",escape(k),escape(v)))
		end
		httpc.request("POST", host, url, recvheader, header, table.concat(body , "&"))
	]]
	local ok, status, body = pcall(httpc.post, authenticate_backend, authenticate_endpoint, postfields, recvheader)
	logger.debug("logind", "ok", ok, "status", status, "body", body)

	if ok and status == 200 then 
		uid = body
	end 

	return uid 
end 


local function authenticate(sdk_platform, token) -- (SDK, user_pass)
	local uid 

	if authenticators[sdk_platform] then 
		local f = authenticators[sdk_platform]
		uid = f(sdk_platform, token)

		if not uid then 
			logger.warn("logind", "verify sdk_platform", sdk_platform, "token", token, "failed to retrieve uid")
			error(string.format("sdk_platform: %s unexpected error verify token: %s", sdk_platform, token))
		end 
	else 
		logger.error("logind", "invalid sdk_platform id", sdk_platform)
		error("invalid sdk_platform: " .. sdk_platform)
	end 

	return uid 
end 

function server.signup_handler(token)
	-- token: base64:(addr:port$user:pass) 
	local addr_port, user, pass = token:match("([^$]+)$([^:]+):([^:]+)")
	user_pass = crypt.base64decode(user) .. "\t" .. crypt.base64decode(pass)

	local uuid = snowflake.snowflake()

	local postfields = {
		addr_port = addr_port,
		uuid = uuid, 
		token = user_pass
	}

	local recvheader = {}

	local ok, status, JS_body = pcall(httpc.post, signup_backend, signup_endpoint, postfields, recvheader)
	local body = cjson.decode(JS_body)

	if ok and status == 200 then 
		if body.status then 
			return true, body
		end 
	end

	return false, body
end

function server.auth_handler(token)
	logger.info("logind", "auth_handler token", token)

	-- the token is base64(SDK):base64(user\tpass)@base64(server)
	local sdk_platform, user_pass, server = token:match("([^:]+):([^@]+)@(.+)")
	sdk_platform = crypt.base64decode(sdk_platform)
	user_pass = crypt.base64decode(user_pass)
	server = crypt.base64decode(server)

	local ok, uid = pcall(authenticate, sdk_platform, user_pass)
	uid = assert(tonumber(uid), "uid not a number")

	if not ok or not uid then 
		logger.info("logind", "authenticate failed, token", token)
		error("authenticate failed")
	end 

	return server, uid
end

function server.login_handler(server, uid, secret)
	if not server_list[server] then 
		logger.error("logind", "user", uid, "want login to unknown server", server)
		error("Unknown server")
	end 

	local gameserver = server_list[server]

	-- only one can login, because disallow multilogin
	local last = user_online[uid]
	if last then
		logger.info("logind", "user", uid, "is already online, notify gateway to kick this user")
		skynet.call(last.gameserver, "lua", "kick", uid, last.subid)
	end
	if user_online[uid] then
		error(string.format("user %s is already online", uid))
	end

	local subid = tostring(skynet.call(gameserver, "lua", "login", uid, secret))

	user_online[uid] = { 
		uid = tostring(uid), -- 仅这一步，编码传回客户端需要转string，不然会损失精度
		subid = subid , 
		servername = server, -- gate_name
		gameserver = gameserver, -- gate_addr
		secret = secret,
	}

	online_counts = online_counts + 1

	logger.info("logind", "user", uid, "login at server", server)	
	-- print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret)))
	return cjson.encode(user_online[uid])
end

local CMD = {}

function CMD.register_gate(server, address)
	server_list[server] = address
end

function CMD.logout(uid, subid)
	local u = user_online[uid]
	if u then
		-- print(string.format("%s@%s is logout", uid, u.servername))
		logger.info("logind", "user", uid, "logout at server", u.server)
		user_online[uid] = nil

		online_counts = online_counts - 1
	end
end

function server.command_handler(command, ...)
	local f = assert(CMD[command])
	return f(...)
end

login(server)
